#  This file is part of OpenLightingDesigner.
# 
#  OpenLightingDesigner is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  OpenLightingDesigner is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with OpenLightingDesigner.  If not, see <http://www.gnu.org/licenses/>.
#
# categorypalette.py
# Copyright (c) 2010 - 2011 Matthew Reid

#import jsonpickle
from Bases import OSCBaseObject, ChildGroup, Serialization

from DeviceSystem.Groups.DeviceGroup import DeviceGroup
from sequencer import LOOP_MODES

class CategoryPaletteGroup(ChildGroup):
    num_palettes = 24
    _saved_class_name = 'CategoryPaletteGroup'
    #_saved_child_objects = ['indexed_items']
    _saved_attributes = ['id']
    def __init__(self, **kwargs):
        self.MainController = kwargs.get('MainController')
        self.EffectsSystem = kwargs.get('EffectsSystem')
        self.attribute_category = kwargs.get('attrib_category')
        self.category = None
        kwargs['child_class'] = CategoryPalette
        super(CategoryPaletteGroup, self).__init__(**kwargs)
        #ChildGroup.__init__(self, **kwargs)
        self.bind(name=self._on_name_set)
        #if 'deserialize' not in kwargs:
        self.id = self.name
        for i in range(self.num_palettes):
            self.build_palette(Index=i+1)
        for c in self.attribute_category.subcategories.itervalues():
            if c.name == self.name:
                self.set_category(c)
        else:
            #self.attribute_category.bind(**dict(zip(['subcategory_added', 'member_update'], [self.on_subcategory_added]*2)))
            self.MainController.DeviceSystem.bind(new_category=self.on_ds_new_category)
        
    def _on_name_set(self, **kwargs):
        self.LOG.info('%s name changed to %s' % (kwargs.get('old'), kwargs.get('value')))
        
    def build_palette(self, **kwargs):
        kwargs.update(dict(MainController=self.MainController, 
                           EffectsSystem=self.EffectsSystem, 
                           category=self.category, 
                           osc_address=str(kwargs.get('Index'))))
        self.add_child(**kwargs)
        
    def set_category(self, category):
        self.LOG.info(self.name, ' category set')
        self.category = category
        for p in self.itervalues():
            p.set_category(category)
            
    def on_ds_new_category(self, **kwargs):
        for c in self.attribute_category.subcategories.itervalues():
            if c.name == self.name:
                self.set_category(c)
                self.MainController.DeviceSystem.unbind(self)
        
    def _deserialize_child(self, d, **kwargs):
        clsname = d['saved_class_name']
        objid = d['attrs']['id']
        if clsname == 'CategoryPalette':
            values = d['attrs']['stored_values']
            newvalues = {}
            for key, val in values.iteritems():
                newvalues[int(key)] = val
            d['attrs']['stored_values'] = newvalues
            self.indexed_items[objid]._load_saved_attr(d)
    
class FXSettings(OSCBaseObject):
    _saved_class_name = 'FXSettings'
    _Properties = {'name':dict(default=''), 
                   'group_name':dict(default=''), 
                   'value_key':dict(type=str), 
                   'function':dict(default='value=math.sin(timeV)'), 
                   'spread':dict(default=100), 
                   'amount':dict(default=100), 
                   'modify_type':dict(default='absolute', entries=['add', 'sub', 'absolute'])}
    _SettingsProperties = ['name', 'group_name', 'value_key', 'function', 
                           'spread', 'amount', 'modify_type']
    def __init__(self, **kwargs):
        '''
        :Parameters:
            'group_name' : name of AttributeGroup
            'value_key' : key in the value dict if the value type is dict,
                otherwise omit
            'function' : function string to evaluate the value given the sequencer time
                (timeV).  example: 'value=math.sin(timeV)'
            'spread' : amount (in percent) to spread across the members (in indexed order)
                of the palette. default is 100.
            'amount' : amount (in percent) that the fx actually modify the values.
                default is 100.
            'modify_type' : how the values will be modified. possible values are
                ['add', 'sub', 'absolute']. default is 'absolute'.
        '''
        self.parent = kwargs.get('parent')
        super(FXSettings, self).__init__(**kwargs)
        if 'deserialize' not in kwargs:
            for key in FXSettings._SettingsProperties:
                if key not in kwargs:
                    continue
                setattr(self, key, kwargs[key])
        if self.value_key == self.group_name:
            self.value_key = None
        self.bind(property_changed=self.on_own_property_changed)
    @property
    def id(self):
        if self.value_key is not None:
            return '__'.join([self.group_name, self.value_key])
        return self.group_name
    
    def get_controller_keys(self, keys=None):
        if keys is None:
            keys = self.parent.stored_values.keys()
        return ['__'.join([self.group_name, str(i)]) for i in keys]
        
#    @property
#    def prototypes(self):
#        d = {}
#        for key in self.controller_keys:
#            e = self.parent.prototype.effects.get(key)
#            if e is not None:
#                d[key] = e
#        return d
        
    def calc_time_offsets(self, keys=None):
        d = {}
        if keys is None:
            keys = self.parent.stored_values
        size = float(len(keys))
        if size in [0., 1.]:
            return dict(zip(keys, [0.]*len(keys)))
        for i in keys:
            d[i] = i / size * self.spread / 100
        return d
        
    def update_effect_palette(self, **kwargs):
        palette = kwargs.get('palette', self.parent.prototype)
        group_keys = kwargs.get('group_keys', self.parent.stored_values.keys())
        obj_to_add = kwargs.get('obj_to_add')
        fxkwargs = dict(controller_type='Functional', 
                        group_names=[self.group_name])
        if self.value_key:
            expr = {self.value_key:self.function}
        else:
            expr = self.function
        toffsets = self.calc_time_offsets(group_keys)
        for ekey in self.get_controller_keys(group_keys):
            index = int(ekey.split('__')[1])
            e = palette.effects.get(ekey)
            if e is None:
                fxkwargs.update(dict(id=ekey, name=ekey))
                e = palette.add_effect(**fxkwargs)
            if obj_to_add is not None:
                e.add_obj(obj_to_add[index])
            e.set_expression(expression=expr)
            offset = toffsets.get(index)
            if offset is not None:
                if self.value_key:
                    offset = {self.value_key:offset}
                e.set_time_offset(offset=offset)
        
    def on_own_property_changed(self, **kwargs):
        prop = kwargs.get('Property')
        value = kwargs.get('value')
        self.update_effect_palette()
#        if self.value_key is not None:
#            expr = {self.value_key:value}
#        else:
#            expr = value
#        if prop.name == 'function':
#            for e in self.prototypes.itervalues():
#                e.set_expression(expression=expr)

class CategoryPalette(OSCBaseObject):
    _saved_attributes = ['name', 'id', 'stored_values', 'stored_fx', 
                         'loop_mode', 'duration', 'palette_type']
    _saved_class_name = 'CategoryPalette'
    _saved_child_objects = ['fx_settings']
    _Properties = {'name':dict(type=str), 
                   'active':dict(default=False, quiet=True), 
                   'group_index':dict(type=int, quiet=True), 
                   'stored_values':dict(default={}), 
                   'stored_fx':dict(default={}), 
                   'loop_mode':dict(default='single'), 
                   'duration':dict(default=2., min=0., max=1024., ignore_type=True), 
                   'palette_type':dict(default='normal', entries=('normal', 'fx'))}
    _ChildGroups = {'fx_settings':{'child_class':FXSettings}}
    loop_modes = LOOP_MODES
    def __init__(self, **kwargs):
        self.MainController = kwargs.get('MainController')
        self.EffectsSystem = kwargs.get('EffectsSystem')
        self.category = kwargs.get('category')
        self.fx_by_group_name = {}
        self.stored_values_set_by_osc = False
        self.groups_set_by_osc = False
        self.dummy_groups = {}
        super(CategoryPalette, self).__init__(**kwargs)
        if 'deserialize' not in kwargs:
            self.name = kwargs.get('name', str(self.Index))
            self.id = self.Index#setID(kwargs.get('id'))
            #self.stored_values = {}
            self.loop_mode = kwargs.get('loop_mode', 'single')
            duration = 2
            if self.name == 'Beam':
                duration = .1
            self.duration = kwargs.get('duration', duration)
            
#            self.snapshot = Snapshot(MainController=self.MainController, 
#                                     EffectsSystem=self.EffectsSystem, 
#                                     osc_parent_node=self.osc_node, 
#                                     osc_address='snapshot', 
#                                     store_by_index=True)
        if self.category is not None:
            self.set_category(self.category)
        self.prototype = self.EffectsSystem.add_palette(name=self.name, all_groups=True)
        self.prototype.add_effect(controller_type='Keyframe', name='Fade', id='Fade')
        
        for fx in self.fx_settings.itervalues():
            fx.update_effect_palette()
        
        self.palette = None
        
        self.bind(active=self._on_active_set, 
                  group_index=self._on_group_index_set, 
                  stored_values=self._on_stored_values_set, 
                  palette_type=self._on_palette_type_set)
                  
        #self.snapshot.bind(sequencer_state=self.on_sequencer_state)
        for key in ['name', 'loop_mode', 'duration']:
            self.add_osc_handler(Property=key)       
        self.add_osc_handler(callbacks={'stored_values':self.on_osc_set_stored_values})
                                        #'set_groups':self.on_osc_set_groups})
    def set_category(self, category):
        self.category = category
        #self.snapshot.set_category(category)
        
    def store_snapshot(self, **kwargs):
        group = kwargs.get('group')
        groups = kwargs.get('groups', [])
        devices = kwargs.get('devices', [])
        to_store = []
        if group:
            to_store = [m.member for m in group.member_order.indexed_items.values()]
        elif len(groups):
            to_store = groups
        elif len(devices):
            to_store = devices
        for i, obj in enumerate(to_store):
            if getattr(obj, 'base_class') == 'Group':
                d = obj.get_objects(captured_only=True)
                attrgroups = d['AttributeGroup'].values() + d['ParameterGroup'].values()
            elif getattr(obj, 'saved_class_name', None) == 'PatchedDevice':
                attrgroups = obj.Groups.values() + obj.ParameterGroups.values()
            else:
                attrgroups = []
            for attrgroup in attrgroups:
                if attrgroup not in self.category.members:
                    continue
                if i not in self.stored_values:
                    self.stored_values[i] = {}
                valkey = 'value'
                if hasattr(attrgroup, 'value_attribute'):
                    valkey = attrgroup.value_attribute
                value = getattr(attrgroup, valkey)
                if isinstance(value, dict):
                    value = value.copy()
                elif isinstance(value, list):
                    value = value[:]
                self.stored_values[i][attrgroup.name] = value
                self.prototype.add_group_name(attrgroup.name)
        #to_store = dict(zip([str(i) for i in range(len(to_store))], to_store))
        #self.clear()
        #self.snapshot.add_member(**to_store)
        #self.snapshot.clear()
        #print 'to_store:', to_store
        
    def add_ChildGroup(self, **kwargs):
        cg = super(CategoryPalette, self).add_ChildGroup(**kwargs)
        cg.bind(child_update=self._on_fx_settings_child_update)
        return cg
        
    def ChildGroup_prepare_child_instance(self, childgroup, cls, **kwargs):
        kwargs['parent'] = self
        return cls, kwargs
        
    def blah_deserialize_child(self, d):
        if d['saved_class_name'] == 'FXSettings':
            fxkwargs = dict(deserialize=d, parent=self, Index=d['attrs']['Index'])
            fx = self.fx_settings.add_child(**fxkwargs)
            if fx is None:
                fx = self.fx_settings.get(d['attrs']['group_name'])
            if fx.group_name not in self.fx_by_group_name:
                self.fx_by_group_name[fx.group_name] = {}
            self.fx_by_group_name[fx.group_name][fx.value_key] = fx
            #if hasattr(self, 'prototype'):
            #    fx.update_effect_palette()
            return fx
        return super(CategoryPalette, self)._deserialize_child(d)
        
    def add_fx(self, **kwargs):
        fx = self.fx_settings.add_child(**kwargs)
        return fx
        
    def del_fx(self, **kwargs):
        fxid = kwargs.get('id')
        fx = kwargs.get('obj')
        if fx is None:
            fx = self.fx_settings.get(fxid)
        if fx is None:
            return
        self.fx_settings.del_child(fx)
        
    def _on_fx_settings_child_update(self, **kwargs):
        mode = kwargs.get('mode')
        fx = kwargs.get('obj')
        if mode == 'add':
            if fx.group_name not in self.fx_by_group_name:
                self.fx_by_group_name[fx.group_name] = {}
            self.fx_by_group_name[fx.group_name][fx.value_key] = fx
        elif mode == 'remove':
            del self.fx_by_group_name[fx.group_name][fx.value_key]
            if not len(self.fx_by_group_name[fx.group_name]):
                del self.fx_by_group_name[fx.group_name]
                for ckey in fx.controller_keys:
                    self.prototype.del_effect(id=ckey)
    
    def apply_palette(self, **kwargs):
        sq = kwargs.get('sequencer')
        pkwargs = dict(palette=self.prototype, auto_start=False, loop_mode=self.loop_mode)
        if sq is not None:
            pkwargs['sequencer'] = sq
        palette = self.EffectsSystem.apply_palette(**pkwargs)
        return palette
        
    def clear_palette(self):
        if self.palette:
            self.palette.unbind(self)
            self.EffectsSystem.clear_palette(id=self.palette.id)
            self.palette = None
            for group in self.dummy_groups.itervalues():
                group.unlink()
            self.dummy_groups.clear()
        
    def set_groups(self, **kwargs):
        group = kwargs.get('group')
        groups = kwargs.get('groups', [])
        devices = kwargs.get('devices', [])
        sequencer = kwargs.get('sequencer')
        autostart = kwargs.get('autostart', True)
        to_add = []
        if group:
            group.add_active_palette(category_palette=self, is_parent=True)
            to_add = [m.member for m in group.member_order.indexed_items.values()]
            self.group_index = group.Index
            
        elif len(groups):
            to_add = groups
        elif len(devices):
            to_add = devices
        fx_to_add = {}
        palette = self.apply_palette(sequencer=sequencer)
        index = 0
        for i, obj in enumerate(to_add):
            #obj.del_active_palette(category=self.category.name)
            dummy_groups = {}
            if getattr(obj, 'base_class') == 'Group':
                d = obj.get_objects()#captured_only=True)
                attrgroups = d['AttributeGroup'].values() + d['ParameterGroup'].values()
            elif getattr(obj, 'saved_class_name', None) == 'PatchedDevice':
                attrgroups = obj.Groups.values() + obj.ParameterGroups.values()
            else:
                attrgroups = []
            fx_to_add[i] = {}
            for attrgroup in attrgroups:
                if attrgroup not in self.category.members:
                    continue
                if index not in self.stored_values:
                    index = 0
                value = self.stored_values.get(index, {}).get(attrgroup.name)
                if value is None:
                    continue
                grkwargs = dict(id='_'.join([attrgroup.id, self.category.name, str(self.Index)]))
                if attrgroup.saved_class_name == 'ParameterGroup':
                    grkwargs['attribute_name'] = attrgroup.attribute_name
                newgroup = attrgroup.__class__(**grkwargs)
                newgroup.add_member(**{attrgroup.id:attrgroup})
                dummy_groups[newgroup.id] = newgroup
                #self.palette.add_obj(attrgroup)
                
                if attrgroup.name in self.fx_by_group_name:
                    fx_to_add[i][attrgroup.name] = newgroup
                else:
                    e = palette.effects['Fade']
                    e.add_obj(newgroup)
                    if isinstance(value, dict):
                        timeV = {}
                        for vkey, vval in value.iteritems():
                            if type(vval) == str:
                                timeV[vkey] = 0.1
                            else:
                                timeV[vkey] = 1.0
                    elif type(value) == str:
                        timeV = 0.1
                    else:
                        timeV = 1.0
                    e.store_value(objkey=newgroup.id, value=value, timeV=timeV)
            obj.add_active_palette(palette=palette, category_palette=self, groups=dummy_groups)
            index += 1
        group_keys = fx_to_add.keys()
        for i, d in fx_to_add.iteritems():
            for name, newgroup in d.iteritems():
                for fx in self.fx_by_group_name[name].itervalues():
                    fx.update_effect_palette(palette=palette, 
                                             group_keys=group_keys,  
                                             obj_to_add=dict(zip(group_keys, [fx_to_add[key][name] for key in group_keys])))
#        if not self.groups_set_by_osc:
#            osckwargs = dict(address='set_groups', value=group.id)
#            if not self.osc_node.oscMaster:
#                osckwargs['to_master'] = True
#            self.osc_node.send_message(**osckwargs)
        if self.osc_node.oscMaster:
            palette.effects['Fade'].store_value(timeV=0.)
            if sequencer is None:
                palette.sequencer.duration = self.duration
                if autostart:
                    palette.sequencer.start()
        return palette
        
    def _on_palette_type_set(self, **kwargs):
        old = kwargs.get('old')
        value = kwargs.get('value')
        if value == old and None not in [value, old]:
            return
        self.stored_values.clear()
        self.EffectsSystem.del_palette(palette=self.prototype)
        self.prototype = self.EffectsSystem.add_palette(name=self.name, all_groups=True)
        if value == 'normal':
            self.prototype.add_effect(controller_type='Keyframe', name='Fade', id='Fade')
            
    def _on_stored_values_set(self, **kwargs):
        if self.stored_values_set_by_osc:
            return
        s = Serialization.to_json(self.stored_values.copy())
        osckwargs = dict(value=s, address='stored_values')
        #if not self.osc_node.oscMaster:
        #    osckwargs['to_master'] = True
        self.osc_node.send_message(**osckwargs)
        
    def on_osc_set_stored_values(self, **kwargs):
        value = kwargs.get('values')[0]
        self.stored_values_set_by_osc = True
        self.stored_values.clear()
        d = Serialization.from_json(value)
        self.stored_values = d
        self.stored_values_set_by_osc = False
        
#    def on_osc_set_groups(self, **kwargs):
#        groupid = kwargs.get('values')[0]
#        group = self.MainController.Groups.get(groupid)
#        self.groups_set_by_osc = True
#        self.set_groups(group=group)
#        self.groups_set_by_osc = False
        
    def _on_active_set(self, **kwargs):
        return
        state = kwargs.get('value')
        if state and self.palette and self.osc_node.oscMaster:
            self.palette.effects['Fade'].store_value(timeV=0.)
            self.palette.sequencer.duration = self.duration
            self.palette.sequencer.start()
        #self.snapshot.active = state
        self.LOG.info('palette active: ', state)
        
    def _on_group_index_set(self, **kwargs):
        return
        value = kwargs.get('value')
        if self.osc_handlers['group_index'].Property_set_by_osc:
            group = self.MainController.Groups.indexed_items.get(value)
            self.set_groups(group=group)
            
    def clear(self):
        #self.snapshot.clear()
        pass
        
    def on_sequencer_state(self, **kwargs):
        return
        state = kwargs.get('state')
        if state != self.active:
            self.active = state
        #if not state:
        #    self.active = False
        #print 'sequencer state: ', kwargs
        
    def _load_saved_attr(self, d, **kwargs):
        self.stored_values_set_by_osc = True
        super(CategoryPalette, self)._load_saved_attr(d, **kwargs)
        self.stored_values_set_by_osc = False
        
    def _get_saved_attr(self, **kwargs):
        if not len(self.stored_values):
            return False
        return super(CategoryPalette, self)._get_saved_attr(**kwargs)


